package de.fhaugsburg.games.boardgames.scrabble;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import de.fhaugsburg.games.boardgames.CircularList;
import de.fhaugsburg.games.boardgames.GridPoint;
import de.fhaugsburg.games.boardgames.ModelChangedListener;
import de.fhaugsburg.games.boardgames.Player.PlayerType;
import de.fhaugsburg.games.boardgames.exceptions.GameException;
import de.fhaugsburg.games.boardgames.exceptions.IllegalMoveException;
import de.fhaugsburg.games.boardgames.scrabble.consoleui.IGuiMessage;
import de.fhaugsburg.games.boardgames.scrabble.robot.GreedyScrabbleBot;
import de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.LetterPiece;
import de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.ScrabbleBoard;
import de.fhaugsburg.games.boardgames.scrabble.wordlist.IWordList;
import de.fhaugsburg.games.boardgames.scrabble.wordlist.WordTrieList;



/**
 * @author klaus
 *
 */
/**
 * @author klaus
 *
 */
public class ScrabbleEngine implements IScrabbleEngine {

	
	private ScrabbleBoard board;

	//all players in the game (CircularList)
	private CircularList<ScrabblePlayer> player = new CircularList<ScrabblePlayer>(4);
	
	private State gameState;
	private IGuiMessage gui;
	private ScrabbleLetterSack letterSack;
	private WordManager wordManager;
	private int roundCount, passesInThisRound;
	private List<String> lastCommitedWords;
	private String gameMessage;

	private IWordList wordList;
	
	//Eventlisteners from the GUI
    private List listeners = new Vector();
	
	
	/**
	 * Constructor
	 * @param board
	 */
	public ScrabbleEngine(ScrabbleBoard board) {
		this.board = board;
		this.letterSack = new ScrabbleLetterSack();
		this.wordManager = new WordManager(this.board);
		
		loadWordList();		//loads the entire wordlist used by the game

		setGameState(State.INITIAL);
		newGame();
	}

	/**
	 * Constructor
	 * @param board
	 * @param letterSack
	 * @param wordManager
	 */
	public ScrabbleEngine(ScrabbleBoard board, ScrabbleLetterSack letterSack, WordManager wordManager) {
		this.board = board;
		this.letterSack = letterSack;
		this.wordManager = wordManager;
		loadWordList();

		
		setGameState(State.INITIAL);
		newGame();
	}

	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#registerGui(de.fhaugsburg.games.boardgames.scrabble.consoleui.IGuiMessage)
	 */
	public void registerGui(de.fhaugsburg.games.boardgames.scrabble.consoleui.IGuiMessage guiMessage){
		this.gui = guiMessage;		
	}
	

	
	
	/**
	 * the eventlisteners for the GUI
	 * @param mcl
	 */
	public void addModelChangedListener(ModelChangedListener mcl) {
	     listeners.add(mcl);
    }
    public void removeModelChangedListener(ModelChangedListener mcl) {
        listeners.remove(mcl);
    }
    private void notifyListeners() {
        Iterator it = listeners.iterator();
        while (it.hasNext()) {
            ModelChangedListener listener = (ModelChangedListener) it.next();
            listener.modelChanged(new ModelChangedEvent(this));
        }
    }

    
    
	
	
	/**
	 * GUI registered ?
	 * @return
	 */
	private boolean guiAvailable(){
		return (gui != null);
	}

	
	
	/**
	 * this is called whenever user interface needs to be repainted
	 */
	private void requiresRepaint() {
		if (guiAvailable()) gui.repaint();
		//notifies the GUI -> something has changed
		notifyListeners();
	}

	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#newGame()
	 */
	public void newGame() {
		//new Sack full of letters
		//letterSack = new ScrabbleLetterSack();
		letterSack.reset();
		//initialize WordManager 
		//wordManager = new WordManager(board);
		wordManager.reset();
		// start over
		resetGame();
	}

	
	
	
	/**
	 * loads WordList from resource
	 */
	private void loadWordList(){
		wordList = new WordTrieList();
	}
	
	
	
	

	/**
	 * resets all game variables to start a new game
	 */
	private void resetGame(){
		//reset board to prepare a new game
		this.board.reset();
		this.roundCount = 0;
		this.passesInThisRound = 0;
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#startGame()
	 */
	public void startGame() throws GameException{
		if (player.getAll().size() < 2){
			throw new GameException("before starting the game, please add at least 2 players");
		}
		newPlayersTurn();
//		requiresRepaint();
	}
	
	
	/**
	 * new round
	 */
	private void newRound(){
		this.passesInThisRound = 0;
		this.roundCount++;
	}
	
	
	
	/**
	 * new players turn, moves CPU player, if currentplayer is
	 */
	private void newPlayersTurn(){
		setGameState(State.DROPPING);
		requiresRepaint();

		//moves the cpuplayer
		if(getActivePlayer().isCpuPlayer()){
			try {
				getActivePlayer().getCpuPlayer().makeMove();
				//requiresRepaint();
				//commitAggreed();
			}catch(GameException e){
				passMove();
			}
		}
	}
	
	
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#addPlayer(java.lang.String, boolean)
	 */
	public int addPlayer(String playerName, PlayerType playerType) throws GameException{
		if (player.getAll().size() < MAX_PLAYERS){
			ScrabblePlayer newPlayer = null;
			
			switch (playerType){
			case LOCAL:
				newPlayer = player.add (new ScrabblePlayer(playerName, this.board, playerType));
				break;
			case CPU:
				newPlayer = player.add (new ScrabblePlayer(playerName, this.board, playerType, new GreedyScrabbleBot(this, wordList)));
				break;
			case TERMINAL:
				newPlayer = player.add (new ScrabblePlayer(playerName, this.board, playerType));
				break;
			}
			newPlayer.recieveAll(letterSack.take(MAX_PIECES));
			return newPlayer.getId();
		}else{
			throw new GameException("Scrabble can be played only by a max number of 4 players");
		}
	}
	
	
	


	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#getActivePlayer()
	 */
	public ScrabblePlayer getActivePlayer(){
		return player.current();
	}
	
	
	
	
	/**
	 * next players turn in local variable
	 */
	private void nextPlayer(){
		refillPlayersPieces();
//		setGameState(State.DROPPING);
		if (!checkWinningCondition()){
			if (player.isOnFirst()){
				newRound();
			}
			player.next();
			newPlayersTurn();
		}
	}
	
	

	/**
	 * get pieces for active player to always hold 7 pieces
	 */
	private void refillPlayersPieces(){
		getActivePlayer().recieveAll(letterSack.take(MAX_PIECES - getActivePlayer().getMyPieces().size()));
	}
	
	
	/**
	 * exchanges all Pieces of the active player
	 * @throws IllegalMoveException
	 */
	public void exchangeAllPlayersPieces() throws IllegalMoveException {
		// first return all Pieces from player to ScrabbleLetterSack
		List<LetterPiece> tmp = getActivePlayer().getMyPieces();
		// return pieces to letterSack
		for (LetterPiece piece : tmp) {
			letterSack.put(piece);
		}
		// now get new pieces
		getActivePlayer().exchangeAllPieces(letterSack.take(MAX_PIECES));
		
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#setLetter(de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.LetterPiece, int, int)
	 */
	public void setLetter(LetterPiece letter, int row, int column) throws IllegalMoveException{
		GridPoint gridPoint = new GridPoint(row, column);
		if (!board.isOnTheBoard(gridPoint)){
			throw new IllegalMoveException("given location is not on the board");
		}else if (!getActivePlayer().hasPiece(letter)){
			throw new IllegalMoveException("you dont have the letter " + letter.getSymbol());
		}
		wordManager.addPiece(letter, gridPoint);
		getActivePlayer().giveAway(letter);
		//this.board.putPiece(letter, row, column);
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#setLetter(de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.LetterPiece, de.fhaugsburg.games.boardgames.GridPoint)
	 */
	public void setLetter(LetterPiece letter, GridPoint gridPoint) throws IllegalMoveException{
		setLetter(letter, gridPoint.getRow(), gridPoint.getColumn());
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#removeLetter(int, int)
	 */
	public void removeLetter(int row, int column) throws IllegalMoveException{
		GridPoint gridPoint = new GridPoint(row, column);
		if (!board.isOnTheBoard(gridPoint)){
			throw new IllegalMoveException("given location is not on the board");
		}else if (!wordManager.contains(gridPoint)){
			throw new IllegalMoveException("no removable letter found at given location");
		}
		getActivePlayer().recieve(wordManager.removePiece(gridPoint));
	}
	
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#removeletter(de.fhaugsburg.games.boardgames.GridPoint)
	 */
	public void removeLetter(GridPoint gridPoint) throws IllegalMoveException{
		removeLetter(gridPoint.getRow(), gridPoint.getColumn());
	}
	
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#removeAllLetters()
	 */
	public void removeAllLetters(){
		undoLastMove();
	}
	
	
	
	/**
	 * first move in the game?
	 * @return
	 */
	private boolean isFirstMove(){
		//return (roundCount == 0) && (player.isOnFirst());
		return board.isEmpty();
	}
	
	
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#commitMove()
	 */
	public void commitMove() throws IllegalMoveException{
		//already put some letters?
		if (wordManager.letterCount() < 1){
			throw new IllegalMoveException("please put letters, before committing");
		}
		//on first move
		if (isFirstMove()) {
			// check if first commit, h 8 filled?
			if (board.getPiece(new GridPoint(7,7)) == null) {
				throw new IllegalMoveException("first player must fill the star");
			}
		//not first move?
		}else{
			//letters must be connected
			if (!wordManager.isConnectedToOtherWords()){
				throw new IllegalMoveException("your word has to be aligned to at least one letter on the board");
			}
		}
		// check for white spaces
		if (wordManager.containsWhiteSpace()){
			throw new IllegalMoveException("no whitespaces allowed");
		}
		//ok
		setGameState(State.COMMITED);
		lastCommitedWords = wordManager.getProducedWords();
		
		
		if (areProducedWordsValid(lastCommitedWords)){
			commitAggreed();
		}else{
			commitRejected();
		}
	}
	
	
	
	/**
	 * checks, if produced words can be agreed	
	 * @param producedWords
	 * @return
	 */
	private boolean areProducedWordsValid(List<String> producedWords){
		return wordList.contains(producedWords);
	}
	
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#commitAggreed()
	 */
	public void commitAggreed(){
		//burn letters on board
		wordManager.commitLetterSequence(true);
		//score the points for current player
		int newscore = wordManager.calcScore();
		getActivePlayer().addCredits(newscore);
		setGameMessage("Player " + getActivePlayer().getPlayerName() + " scored " + newscore + " points.");
		
		//reset tmp memory for letters to prepare the next move
		wordManager.reset();
		nextPlayer();
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#commitRejected()
	 */
	public void commitRejected(){
		setGameMessage("Player " + getActivePlayer().getPlayerName() + "'s commit was rejected.");
		undoLastMove();
		setGameState(State.DROPPING);
	}
	
	
	
	/**
	 * retake all pieced laid on the board
	 */
	public void undoLastMove(){
		ArrayList<LetterPiece> tempPieces = new ArrayList<LetterPiece>(wordManager.removePreliminaryPieces());
		getActivePlayer().recieveAll(tempPieces);
	}
	
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#passMove()
	 */
	public void passMove(){
		setGameMessage("Player " + getActivePlayer().getPlayerName() + " passed.");
		undoLastMove();
//		requiresRepaint();
		nextPlayer();
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#getLastProdudedWordsAsString()
	 */
	public String getLastProdudedWordsAsString(){
		if (lastCommitedWords == null){
			return "";
		}else{
			return lastCommitedWords.toString();
		}
	}
	
	
	/**
	 * checks for winning condition
	 * @return
	 */
	private boolean checkWinningCondition(){
		if (letterSack.isEmpty() && passesInThisRound >= player.getAll().size()){
			setGameState(State.GAMEOVER);
			return true;
		}
		return false;
	}
	
	
	
	/**
	 * Sets state of the game
	 * @param gameState
	 */
	private void setGameState(State gameState) {
		this.gameState = gameState;
	}


	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#getGameState()
	 */
	public State getGameState() {
		return gameState;
	}

	
	
	

	/**
	 * @param gameMessage the gameMessage to set
	 */
	private void setGameMessage(String gameMessage) {
		this.gameMessage = gameMessage;
	}



	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#getGameMessage()
	 */
	public String getGameMessage() {
		if (this.gameMessage == null || this.gameMessage.equals("")) return "";
		String returnString = new String(this.gameMessage);
		gameMessage = "";
		return returnString;
	}
	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#gameMessageAvailable()
	 */
	public boolean gameMessageAvailable(){
		if (this.gameMessage == null || this.gameMessage.equals("")) return false;
		else return true;
	}

	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#getBoard()
	 */
	public ScrabbleBoard getBoard() {
		return this.board;
	}
	
	
	/**
	 * get the WordManager helperclass instance
	 * @return
	 */
	public WordManager getWordManager(){
		return this.wordManager;
	}

	
	
	/* (non-Javadoc)
	 * @see de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine#getNumberOfPlayers()
	 */
	public int getNumberOfPlayers() {
		return player.getAll().size();
	}
	
}
